Welcome to my notebook covering the use of the tidycensus R package
for extracting and evaluating census data in the state of Oregon. In
part A We are interested in uncovering the percentages of the population
with graduate level degrees at a county level. Part B will continue to
focus on education data, Stay tuned to find out more!
Import Libraries
Start by importing the list of libraries that will assist us in
connecting with the Census Bureau’s APIs and visualizing the desired
data.
library(tidycensus) # connect with the census bureau API
library(tidyverse) # data wrangling
library(mapview) # spatial data visualizer
library(plotly) # graph / chart visualizer
library(ggiraph) # graph / chart visualizer
library(ggplot2) # graph / chart visualizer
library(DT) # fancy tables
library(scales) # scales map data to aesthetics
library(sf) # spatial data package
Part A
Data Source
We will use the get_acs() function from the tidycensus package to
pull data from the census bureau. The function defaults to the 2017-2021
5-year ACS dataset. Which is necessary for us, since there are a list of
counties in Oregon with a population of less than 65,000.
The data we would like to visualize is the percent of the population
that have a graduate degree at the county level in the state of
Oregon.
# use the get_acs() function for importing data
or_grads <- get_acs(
geography = "county", # county level geography
variables = "DP02_0066P", # variable for percent of the population that have a graduate degrees
state = "OR",
year = 2021
)
Data Manipulation & DataTables
Data Visualization & Analysis
All tables in the tidycensus package come with a margin of error
column to give us an understanding of accuracy in the dataset.
Typically, census data from geographies of larger population have a
lower margin of error.
Lets plot the MOE variable with our estimate variable below.
# create plot revealing moe and estimate variables
or_plot_errorbar <- ggplot(or_grads, aes(x = estimate, y = reorder(NAME, estimate))) + # sort by counties high to low
geom_errorbar(aes(xmin = estimate - moe, xmax = estimate + moe),
width = 0.5, linewidth = 0.5) +
geom_point(color = "gold", size = 3) + # state color gold for point data
scale_x_continuous(labels = function(x) paste0(x, '%')) + # concatenate '%' to estimate data
scale_y_discrete(labels = function(x) str_remove(x, " County, Oregon|, Oregon")) + # strip 'county' from the name column
labs(title = "% Population with graduate degrees, 2017-2021 ACS", # use appropriate title
subtitle = "Counties in Oregon", # use appropriate subtitle
caption = "Data acquired with R and tidycensus. Error bars represent margin of error around estimates.",
"ACS estimate",
y = "",
x = "Percentage") +
theme_minimal(base_size = 12) # remove tick marks
or_plot_errorbar # print plot

Figure 3 above reveals the margin of error
associated with the estimate data.
Interative Plots
Using ggplotly() I’m going to recreate the plot above but leveraging
the interactivity of the plotly library.
# use ggplotly() and add a tooltip, and extend the margins of the plot
ggplotly(or_plot_errorbar, tooltip = "x")%>%
layout(margin = list(l = 50, r = 50, b = 50, t = 50))
Figure 4 Hover over the points in the plot above to
see the interactive tootip.
Part B
Continuing with the theme of education, lets import more data
through the tidycensus package.
What percentage of people with bachelor’s degrees rent or own
their home in the state of Oregon at the tract level?
To find variables that will assist us in our analysis, we will use
the load_variables() function to filter through the ACS data.
# use the load_variables function() with the year and ACS 5 year parameter
vars <- load_variables(2021, "acs5")
# load vars into a datatable
datatable(vars)
Figure 5 By using the search feature in the
datatable, you see examples of available results. For example search
“bachelor’s” to find variable codes moving forward.
Import Data
We will need to pull in two datasets and eventually merge the
results into one table.
– “B25013_006” is the population of people with bachelor’s degrees
that own their home.
– “B25013_011” is the population of people with bachelor’s degrees
that rent their home.
# import data at the tract level for home owners
bach_owners <- get_acs(
geography = "tract",
variables = "B25013_006",
state = "OR",
geometry = TRUE # get multipolygon data of Oregon tracts
)
# import data at the tract level for renters
bach_renters <- get_acs(
geography = "tract",
variables = "B25013_011",
state = "OR",
geometry = FALSE # geometry is false because we are getting our multipolygon data in the owners dataset.
)
Data Wrangling
Use functions of the tidyverse to rename, select, and mutuate
columns to fit our analysis.
# clarify the name of the estimate & moe columns before joining
bach_owners <- bach_owners %>%
rename(estimate_owners = estimate, moe_owners = moe)
# clarify the name of the estimate & moe columns before joining
bach_renters <- bach_renters %>%
rename(estimate_renters = estimate, moe_renters = moe) %>%
select(GEOID, estimate_renters, moe_renters) # drop unnecessary values
# join dataframes on GEOID, and create percent columns of each category
bach_housing <- left_join(bach_owners, bach_renters, by='GEOID') %>%
select(GEOID, NAME, estimate_owners, estimate_renters, moe_owners, moe_renters, geometry) %>%
mutate(estimate_total = estimate_renters + estimate_owners) %>% # create a total value
mutate(percent_renters = (estimate_renters/estimate_total)*100) %>% # create a % of renters
mutate(percent_owners = (estimate_owners/estimate_total)*100) %>% # create a % of owners
mutate(across(where(is.numeric), ~ round(.x, digits = 2)))
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) :
invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) :
attempt to use zero-length variable name
Data Visualization & Analysis
bach_housing_table <- bach_housing %>%
select(NAME, percent_renters, percent_owners)
datatable(bach_housing_table)
We are going to start with the mapview package to get a glimplse
into the
mapview(bach_housing, zcol = "percent_renters", zcol = "percent_owners")
LS0tDQp0aXRsZTogIkhpZ2hlciBlZHVjYXRpb24gJSBpbiBPcmVnb24gLSBBbGlzIHZvbGF0IHByb3ByaWlzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCiMjIyMgV2VsY29tZSB0byBteSBub3RlYm9vayBjb3ZlcmluZyB0aGUgdXNlIG9mIHRoZSB0aWR5Y2Vuc3VzIFIgcGFja2FnZSBmb3IgZXh0cmFjdGluZyBhbmQgZXZhbHVhdGluZyBjZW5zdXMgZGF0YSBpbiB0aGUgc3RhdGUgb2YgT3JlZ29uLiBJbiBwYXJ0IEEgV2UgYXJlIGludGVyZXN0ZWQgaW4gdW5jb3ZlcmluZyB0aGUgcGVyY2VudGFnZXMgb2YgdGhlIHBvcHVsYXRpb24gd2l0aCBncmFkdWF0ZSBsZXZlbCBkZWdyZWVzIGF0IGEgY291bnR5IGxldmVsLiBQYXJ0IEIgd2lsbCBjb250aW51ZSB0byBmb2N1cyBvbiBlZHVjYXRpb24gZGF0YSwgU3RheSB0dW5lZCB0byBmaW5kIG91dCBtb3JlIQ0KDQojIyMgSW1wb3J0IExpYnJhcmllcw0KDQojIyMjIyBTdGFydCBieSBpbXBvcnRpbmcgdGhlIGxpc3Qgb2YgbGlicmFyaWVzIHRoYXQgd2lsbCBhc3Npc3QgdXMgaW4gY29ubmVjdGluZyB3aXRoIHRoZSBDZW5zdXMgQnVyZWF1J3MgQVBJcyBhbmQgdmlzdWFsaXppbmcgdGhlIGRlc2lyZWQgZGF0YS4gDQpgYGB7Y3NzLCBlY2hvPUZBTFNFfQ0KcCB7DQogIG1hcmdpbi1ib3R0b206IDBweDsNCn0NCmBgYA0KYGBge3IgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpsaWJyYXJ5KHRpZHljZW5zdXMpICMgY29ubmVjdCB3aXRoIHRoZSBjZW5zdXMgYnVyZWF1IEFQSQ0KbGlicmFyeSh0aWR5dmVyc2UpICMgZGF0YSB3cmFuZ2xpbmcNCmxpYnJhcnkobWFwdmlldykgIyBzcGF0aWFsIGRhdGEgdmlzdWFsaXplcg0KbGlicmFyeShwbG90bHkpICMgZ3JhcGggLyBjaGFydCB2aXN1YWxpemVyDQpsaWJyYXJ5KGdnaXJhcGgpICMgZ3JhcGggLyBjaGFydCB2aXN1YWxpemVyDQpsaWJyYXJ5KGdncGxvdDIpICMgZ3JhcGggLyBjaGFydCB2aXN1YWxpemVyDQpsaWJyYXJ5KERUKSAjIGZhbmN5IHRhYmxlcw0KbGlicmFyeShzY2FsZXMpICMgc2NhbGVzIG1hcCBkYXRhIHRvIGFlc3RoZXRpY3MNCmxpYnJhcnkoc2YpICMgc3BhdGlhbCBkYXRhIHBhY2thZ2UNCmBgYA0KIyMjIFBhcnQgQQ0KPGhyIC8+DQojIyMgRGF0YSBTb3VyY2UNCg0KIyMjIyMgV2Ugd2lsbCB1c2UgdGhlIGdldF9hY3MoKSBmdW5jdGlvbiBmcm9tIHRoZSB0aWR5Y2Vuc3VzIHBhY2thZ2UgdG8gcHVsbCBkYXRhIGZyb20gdGhlIGNlbnN1cyBidXJlYXUuIFRoZSBmdW5jdGlvbiBkZWZhdWx0cyB0byB0aGUgMjAxNy0yMDIxIDUteWVhciBBQ1MgZGF0YXNldC4gV2hpY2ggaXMgbmVjZXNzYXJ5IGZvciB1cywgc2luY2UgdGhlcmUgYXJlIGEgbGlzdCBvZiBjb3VudGllcyBpbiBPcmVnb24gd2l0aCBhIHBvcHVsYXRpb24gb2YgbGVzcyB0aGFuIDY1LDAwMC4gDQoNCiMjIyMjIFRoZSBkYXRhIHdlIHdvdWxkIGxpa2UgdG8gdmlzdWFsaXplIGlzIHRoZSBwZXJjZW50IG9mIHRoZSBwb3B1bGF0aW9uIHRoYXQgaGF2ZSBhIGdyYWR1YXRlIGRlZ3JlZSBhdCB0aGUgY291bnR5IGxldmVsIGluIHRoZSBzdGF0ZSBvZiBPcmVnb24uICANCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyB1c2UgdGhlIGdldF9hY3MoKSBmdW5jdGlvbiBmb3IgaW1wb3J0aW5nIGRhdGENCm9yX2dyYWRzIDwtIGdldF9hY3MoDQogIGdlb2dyYXBoeSA9ICJjb3VudHkiLCAjIGNvdW50eSBsZXZlbCBnZW9ncmFwaHkNCiAgdmFyaWFibGVzID0gIkRQMDJfMDA2NlAiLCAjIHZhcmlhYmxlIGZvciBwZXJjZW50IG9mIHRoZSBwb3B1bGF0aW9uIHRoYXQgaGF2ZSBhIGdyYWR1YXRlIGRlZ3JlZXMNCiAgc3RhdGUgPSAiT1IiLCANCiAgeWVhciA9IDIwMjENCikNCmBgYA0KIyMjIERhdGEgTWFuaXB1bGF0aW9uICYgRGF0YVRhYmxlcw0KDQojIyMjIyBBbiBpbXBvcnRhbnQgYXNwZWN0IG9mIHRoZSB0aWR5Y2Vuc3VzIHBhY2thZ2UgaXMgdGhhdCBpcyBwcmVzZW50cyB0aGUgZGF0YSB0byB1cyBpbiBhIHRpZHkgZm9ybWF0LiBUaGUgZGF0YSBpcyBwcmUtY2xlYW5lZCBhbmQgdGFrZXMgbGl0dGxlIHdyYW5nbGluZyB0byBhbnN3ZXIgcXVlc3Rpb25zLg0KYGBge3J9DQojIGZpbHRlciBhbmQgYXJyYW5nZSByZXN1bHRzDQpvcl9ncmFkc190YWJsZSA8LSBvcl9ncmFkcyAlPiUNCiAgc2VwYXJhdGUoTkFNRSwgaW50byA9IGMoImNvdW50eSIsICJzdGF0ZSIpLCBzZXAgPSAiLCAiKSAlPiUgIyBjcmVhdGUgY291bnR5IGFuZCBzdGF0ZSBjb2x1bW5zDQogIGFycmFuZ2UoLWVzdGltYXRlKSAlPiUgIyBzb3J0IGJ5IGVzdGltYXRlIGRlc2NlbmRpbmcNCiAgc2VsZWN0KGNvdW50eSwgc3RhdGUsIGVzdGltYXRlLCBtb2UpICMgc2VsZWN0IGltcG9ydGFudCBjb2x1bW5zDQpgYGANCg0KIyMjIyMgQnkgdXNpbmcgdGhlIGRhdGF0YWJsZSgpIGZ1bmN0aW9uIGZyb20gdGhlIERUIGxpYnJhcnkgd2UgY2FuIGZpbHRlciBvdXIgcmVzdWx0cyB0byBzZWUgdGhlIGRlc2lyZWQgaW5mb3JtYXRpb24uDQpgYGB7cn0NCiMgZnVuY3Rpb24gZnJvbSB0aGUgRFQgbGlicmFyeSB0byBjcmVhdGUgYSBkYXRhdGFibGUNCmRhdGF0YWJsZShvcl9ncmFkc190YWJsZSkNCmBgYA0KKipGaWd1cmUgMSoqIGFib3ZlIHJldmVhbHMgdGhlIGRhdGEgaW1wb3J0ZWQgZnJvbSB0aGUgZ2V0X2FjcygpIGZ1bmN0aW9uLiBSZXN1bHRzIGNvbmNsdWRlIHRoYXQgdGhlIHRvcCAzIGNvdW50aWVzIHdpdGggZ3JhZHVhdGUgZGVncmVlcyBpcyBCZW50b24sIE11bHRub21haCwgYW5kIFdhc2hpbmd0b24uIEJvdHRvbSAzIGNvdW50aWVzIGFyZSBNYWxoZXVyLCBHaWxsaWFtLCBhbmQgTW9ycm93Lg0KDQo8aHIgLz4NCg0KIyMjIERhdGEgVmlzdWFsaXphdGlvbiAmIEFuYWx5c2lzDQoNCiMjIyMjIE5vdyB0aGF0IG91ciBkYXRhIGlzIGltcG9ydGVkIGFuZCBpbiBhIHByb3BlciBmb3JtYXQuIFdlIGNhbiBiZWdpbiB1c2luZyBnZ3Bsb3QgdG8gdmlzdWFsaXplIHRoZSBwZXJjZW50YWdlIG9mIHRoZSBvcmVnb24gcG9wdWxhdGlvbiB0aGF0IGdyYWR1YXRlIGhhdmUgZGVncmVlcy4gDQpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD05LjV9DQojIHVzZSBnZ3Bsb3Qgd2l0aCBlc3RpbWF0ZSBhcyB0aGUgeCBheGlzIGFuZCBjb3VudHkgYXMgdGhlIHkgYXhpcw0Kb3JfcGxvdCA8LSBnZ3Bsb3Qob3JfZ3JhZHNfdGFibGUsIGFlcyh4ID0gZXN0aW1hdGUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gcmVvcmRlcihjb3VudHksIGVzdGltYXRlKSkpICsgIyBzb3J0IGJ5IGNvdW50aWVzIGhpZ2ggdG8gbG93DQogIGdlb21fcG9pbnQoY29sb3I9Im5hdnkiLCBzaXplID0gMykgKyAjIHN0YXRlIGNvbG9yIG5hdnkgYmx1ZSBmb3IgcG9pbnQgZGF0YQ0KICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gZnVuY3Rpb24oeCkgcGFzdGUwKHgsICclJykpICsgIyBjb25jYXRlbmF0ZSAnJScgdG8gZXN0aW1hdGUgZGF0YQ0KICBzY2FsZV95X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl9yZW1vdmUoeCwgIiBDb3VudHkiKSkgKyAjIHN0cmlwICdjb3VudHknIGZyb20gdGhlIG5hbWUgY29sdW1uDQogIGxhYnModGl0bGUgPSAiJSBQb3B1bGF0aW9uIHdpdGggZ3JhZHVhdGUgZGVncmVlcywgMjAxNy0yMDIxIEFDUyIsICMgdXNlIGFwcHJvcHJpYXRlIHRpdGxlDQogICAgICAgc3VidGl0bGUgPSAiQ291bnRpZXMgaW4gT3JlZ29uIiwgIyB1c2UgYXBwcm9wcmlhdGUgc3VidGl0bGUNCiAgICAgICBjYXB0aW9uID0gIkRhdGEgYWNxdWlyZWQgd2l0aCBSIGFuZCB0aWR5Y2Vuc3VzIiwNCiAgICAgICB4ID0gIkFDUyBlc3RpbWF0ZSBQZXJjZW50YWdlIiwNCiAgICAgICB5ID0gIiIpICsNCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgIyByZW1vdmUgdGljayBtYXJrcyANCiAgDQpvcl9wbG90ICMgcHJpbnQgcGxvdA0KYGBgDQoqKkZpZ3VyZSAyKiogYWJvdmUgcGxvdHMgdGhlIGNvdW50aWVzIGJ5IGVzdGltYXRlIHZhbHVlIGluIGEgZGVzY2VuZGluZyBvcmRlci4gV2UgZ2V0IGEgZnVsbCBwaWN0dXJlIG9mIG91ciByZXN1bHRzIHdpdGhvdXQgaGF2aW5nIHRvIGZpbHRlciB0aHJvdWdoIG11bHRpcGxlIHBhZ2VzIGluIGEgdGFibGUuDQoNCjwgaHIgLz4NCg0KIyMjIyMgQWxsIHRhYmxlcyBpbiB0aGUgdGlkeWNlbnN1cyBwYWNrYWdlIGNvbWUgd2l0aCBhIG1hcmdpbiBvZiBlcnJvciBjb2x1bW4gdG8gZ2l2ZSB1cyBhbiB1bmRlcnN0YW5kaW5nIG9mIGFjY3VyYWN5IGluIHRoZSBkYXRhc2V0LiBUeXBpY2FsbHksIGNlbnN1cyBkYXRhIGZyb20gZ2VvZ3JhcGhpZXMgb2YgbGFyZ2VyIHBvcHVsYXRpb24gaGF2ZSBhIGxvd2VyIG1hcmdpbiBvZiBlcnJvci4NCg0KIyMjIyMgTGV0cyBwbG90IHRoZSBNT0UgdmFyaWFibGUgd2l0aCBvdXIgZXN0aW1hdGUgdmFyaWFibGUgYmVsb3cuIA0KYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OS41fQ0KIyBjcmVhdGUgcGxvdCByZXZlYWxpbmcgbW9lIGFuZCBlc3RpbWF0ZSB2YXJpYWJsZXMNCm9yX3Bsb3RfZXJyb3JiYXIgPC0gZ2dwbG90KG9yX2dyYWRzLCBhZXMoeCA9IGVzdGltYXRlLCB5ID0gcmVvcmRlcihOQU1FLCBlc3RpbWF0ZSkpKSArICMgc29ydCBieSBjb3VudGllcyBoaWdoIHRvIGxvdw0KICBnZW9tX2Vycm9yYmFyKGFlcyh4bWluID0gZXN0aW1hdGUgLSBtb2UsIHhtYXggPSBlc3RpbWF0ZSArIG1vZSksIA0KICAgICAgICAgICAgICAgIHdpZHRoID0gMC41LCBsaW5ld2lkdGggPSAwLjUpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJnb2xkIiwgc2l6ZSA9IDMpICsgIyBzdGF0ZSBjb2xvciBnb2xkIGZvciBwb2ludCBkYXRhDQogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBmdW5jdGlvbih4KSBwYXN0ZTAoeCwgJyUnKSkgKyAjIGNvbmNhdGVuYXRlICclJyB0byBlc3RpbWF0ZSBkYXRhDQogIHNjYWxlX3lfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3JlbW92ZSh4LCAiIENvdW50eSwgT3JlZ29ufCwgT3JlZ29uIikpICsgIyBzdHJpcCAnY291bnR5JyBmcm9tIHRoZSBuYW1lIGNvbHVtbg0KICBsYWJzKHRpdGxlID0gIiUgUG9wdWxhdGlvbiB3aXRoIGdyYWR1YXRlIGRlZ3JlZXMsIDIwMTctMjAyMSBBQ1MiLCAjIHVzZSBhcHByb3ByaWF0ZSB0aXRsZQ0KICAgICAgIHN1YnRpdGxlID0gIkNvdW50aWVzIGluIE9yZWdvbiIsICMgdXNlIGFwcHJvcHJpYXRlIHN1YnRpdGxlDQogICAgICAgY2FwdGlvbiA9ICJEYXRhIGFjcXVpcmVkIHdpdGggUiBhbmQgdGlkeWNlbnN1cy4gRXJyb3IgYmFycyByZXByZXNlbnQgbWFyZ2luIG9mIGVycm9yIGFyb3VuZCBlc3RpbWF0ZXMuIiwNCiAgICAgICAiQUNTIGVzdGltYXRlIiwNCiAgICAgIHkgPSAiIiwNCiAgICAgIHggPSAiUGVyY2VudGFnZSIpICsNCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgIyByZW1vdmUgdGljayBtYXJrcyANCiAgDQpvcl9wbG90X2Vycm9yYmFyICMgcHJpbnQgcGxvdA0KYGBgDQoqKkZpZ3VyZSAzKiogYWJvdmUgcmV2ZWFscyB0aGUgbWFyZ2luIG9mIGVycm9yIGFzc29jaWF0ZWQgd2l0aCB0aGUgZXN0aW1hdGUgZGF0YS4NCg0KPGhyIC8+DQoNCiMjIyBJbnRlcmF0aXZlIFBsb3RzDQoNCiMjIyMjIFVzaW5nIGdncGxvdGx5KCkgSSdtIGdvaW5nIHRvIHJlY3JlYXRlIHRoZSBwbG90IGFib3ZlIGJ1dCBsZXZlcmFnaW5nIHRoZSBpbnRlcmFjdGl2aXR5IG9mIHRoZSBwbG90bHkgbGlicmFyeS4NCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTkuNX0NCiMgdXNlIGdncGxvdGx5KCkgYW5kIGFkZCBhIHRvb2x0aXAsIGFuZCBleHRlbmQgdGhlIG1hcmdpbnMgb2YgdGhlIHBsb3QNCmdncGxvdGx5KG9yX3Bsb3RfZXJyb3JiYXIsIHRvb2x0aXAgPSAieCIpJT4lIA0KICBsYXlvdXQobWFyZ2luID0gbGlzdChsID0gNTAsIHIgPSA1MCwgYiA9IDUwLCB0ID0gNTApKQ0KYGBgDQoqKkZpZ3VyZSA0KiogSG92ZXIgb3ZlciB0aGUgcG9pbnRzIGluIHRoZSBwbG90IGFib3ZlIHRvIHNlZSB0aGUgaW50ZXJhY3RpdmUgdG9vdGlwLg0KDQo8aHIgLz4NCg0KIyMjIFBhcnQgQg0KDQo8aHIgLz4NCg0KIyMjIyMgQ29udGludWluZyB3aXRoIHRoZSB0aGVtZSBvZiBlZHVjYXRpb24sIGxldHMgaW1wb3J0IG1vcmUgZGF0YSB0aHJvdWdoIHRoZSB0aWR5Y2Vuc3VzIHBhY2thZ2UuIA0KDQoqKldoYXQgcGVyY2VudGFnZSBvZiBwZW9wbGUgd2l0aCBiYWNoZWxvcidzIGRlZ3JlZXMgcmVudCBvciBvd24gdGhlaXIgaG9tZSBpbiB0aGUgc3RhdGUgb2YgT3JlZ29uIGF0IHRoZSB0cmFjdCBsZXZlbD8qKg0KDQojIyMjIyBUbyBmaW5kIHZhcmlhYmxlcyB0aGF0IHdpbGwgYXNzaXN0IHVzIGluIG91ciBhbmFseXNpcywgd2Ugd2lsbCB1c2UgdGhlIGxvYWRfdmFyaWFibGVzKCkgZnVuY3Rpb24gdG8gZmlsdGVyIHRocm91Z2ggdGhlIEFDUyBkYXRhLiANCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyB1c2UgdGhlIGxvYWRfdmFyaWFibGVzIGZ1bmN0aW9uKCkgd2l0aCB0aGUgeWVhciBhbmQgQUNTIDUgeWVhciBwYXJhbWV0ZXINCnZhcnMgPC0gbG9hZF92YXJpYWJsZXMoMjAyMSwgImFjczUiKQ0KIyBsb2FkIHZhcnMgaW50byBhIGRhdGF0YWJsZQ0KZGF0YXRhYmxlKHZhcnMpDQpgYGANCioqRmlndXJlIDUqKiBCeSB1c2luZyB0aGUgc2VhcmNoIGZlYXR1cmUgaW4gdGhlIGRhdGF0YWJsZSwgeW91IHNlZSBleGFtcGxlcyBvZiBhdmFpbGFibGUgcmVzdWx0cy4gRm9yIGV4YW1wbGUgc2VhcmNoICJiYWNoZWxvcidzIiB0byBmaW5kIHZhcmlhYmxlIGNvZGVzIG1vdmluZyBmb3J3YXJkLiANCg0KPGhyIC8+DQoNCiMjIyBJbXBvcnQgRGF0YQ0KDQojIyMjIyBXZSB3aWxsIG5lZWQgdG8gcHVsbCBpbiB0d28gZGF0YXNldHMgYW5kIGV2ZW50dWFsbHkgbWVyZ2UgdGhlIHJlc3VsdHMgaW50byBvbmUgdGFibGUuIA0KIyMjIyMgLS0gIkIyNTAxM18wMDYiIGlzIHRoZSBwb3B1bGF0aW9uIG9mIHBlb3BsZSB3aXRoIGJhY2hlbG9yJ3MgZGVncmVlcyB0aGF0IG93biB0aGVpciBob21lLg0KIyMjIyMgLS0gIkIyNTAxM18wMTEiIGlzIHRoZSBwb3B1bGF0aW9uIG9mIHBlb3BsZSB3aXRoIGJhY2hlbG9yJ3MgZGVncmVlcyB0aGF0IHJlbnQgdGhlaXIgaG9tZS4NCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KIyBpbXBvcnQgZGF0YSBhdCB0aGUgdHJhY3QgbGV2ZWwgZm9yIGhvbWUgb3duZXJzDQpiYWNoX293bmVycyA8LSBnZXRfYWNzKA0KICBnZW9ncmFwaHkgPSAidHJhY3QiLA0KICB2YXJpYWJsZXMgPSAiQjI1MDEzXzAwNiIsDQogIHN0YXRlID0gIk9SIiwNCiAgZ2VvbWV0cnkgPSBUUlVFICMgZ2V0IG11bHRpcG9seWdvbiBkYXRhIG9mIE9yZWdvbiB0cmFjdHMNCikNCg0KIyBpbXBvcnQgZGF0YSBhdCB0aGUgdHJhY3QgbGV2ZWwgZm9yIHJlbnRlcnMNCmJhY2hfcmVudGVycyA8LSBnZXRfYWNzKA0KICBnZW9ncmFwaHkgPSAidHJhY3QiLA0KICB2YXJpYWJsZXMgPSAiQjI1MDEzXzAxMSIsDQogIHN0YXRlID0gIk9SIiwNCiAgZ2VvbWV0cnkgPSBGQUxTRSAjIGdlb21ldHJ5IGlzIGZhbHNlIGJlY2F1c2Ugd2UgYXJlIGdldHRpbmcgb3VyIG11bHRpcG9seWdvbiBkYXRhIGluIHRoZSBvd25lcnMgZGF0YXNldC4gDQopDQpgYGANCiMjIyBEYXRhIFdyYW5nbGluZw0KDQojIyMjIyBVc2UgZnVuY3Rpb25zIG9mIHRoZSB0aWR5dmVyc2UgdG8gcmVuYW1lLCBzZWxlY3QsIGFuZCBtdXR1YXRlIGNvbHVtbnMgdG8gZml0IG91ciBhbmFseXNpcy4gDQpgYGB7ciBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCiMgY2xhcmlmeSB0aGUgbmFtZSBvZiB0aGUgZXN0aW1hdGUgJiBtb2UgY29sdW1ucyBiZWZvcmUgam9pbmluZw0KYmFjaF9vd25lcnMgPC0gYmFjaF9vd25lcnMgJT4lDQogIHJlbmFtZShlc3RpbWF0ZV9vd25lcnMgPSBlc3RpbWF0ZSwgbW9lX293bmVycyA9IG1vZSkNCg0KIyBjbGFyaWZ5IHRoZSBuYW1lIG9mIHRoZSBlc3RpbWF0ZSAmIG1vZSBjb2x1bW5zIGJlZm9yZSBqb2luaW5nDQpiYWNoX3JlbnRlcnMgPC0gYmFjaF9yZW50ZXJzICU+JQ0KICByZW5hbWUoZXN0aW1hdGVfcmVudGVycyA9IGVzdGltYXRlLCBtb2VfcmVudGVycyA9IG1vZSkgJT4lDQogIHNlbGVjdChHRU9JRCwgZXN0aW1hdGVfcmVudGVycywgbW9lX3JlbnRlcnMpICMgZHJvcCB1bm5lY2Vzc2FyeSB2YWx1ZXMNCg0KIyBqb2luIGRhdGFmcmFtZXMgb24gR0VPSUQsIGFuZCBjcmVhdGUgcGVyY2VudCBjb2x1bW5zIG9mIGVhY2ggY2F0ZWdvcnkNCmJhY2hfaG91c2luZyA8LSBsZWZ0X2pvaW4oYmFjaF9vd25lcnMsIGJhY2hfcmVudGVycywgYnk9J0dFT0lEJykgJT4lDQogIHNlbGVjdChHRU9JRCwgTkFNRSwgZXN0aW1hdGVfb3duZXJzLCBlc3RpbWF0ZV9yZW50ZXJzLCBtb2Vfb3duZXJzLCBtb2VfcmVudGVycywgZ2VvbWV0cnkpICU+JQ0KICBtdXRhdGUoZXN0aW1hdGVfdG90YWwgPSBlc3RpbWF0ZV9yZW50ZXJzICsgZXN0aW1hdGVfb3duZXJzKSAlPiUgIyBjcmVhdGUgYSB0b3RhbCB2YWx1ZQ0KICBtdXRhdGUocGVyY2VudF9yZW50ZXJzID0gKGVzdGltYXRlX3JlbnRlcnMvZXN0aW1hdGVfdG90YWwpKjEwMCkgJT4lICMgY3JlYXRlIGEgJSBvZiByZW50ZXJzDQogIG11dGF0ZShwZXJjZW50X293bmVycyA9IChlc3RpbWF0ZV9vd25lcnMvZXN0aW1hdGVfdG90YWwpKjEwMCkgJT4lICMgY3JlYXRlIGEgJSBvZiBvd25lcnMNCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfiByb3VuZCgueCwgZGlnaXRzID0gMikpKQ0KYGBgDQojIyMgRGF0YSBWaXN1YWxpemF0aW9uICYgQW5hbHlzaXMNCg0KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTkuNSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpiYWNoX2hvdXNpbmdfdGFibGUgPC0gYmFjaF9ob3VzaW5nICU+JQ0KICBzZWxlY3QoTkFNRSwgcGVyY2VudF9yZW50ZXJzLCBwZXJjZW50X293bmVycykgJT4lDQogIGFycmFuZ2UoLXBlcmNlbnRfcmVudGVycykNCg0KDQpkYXRhdGFibGUoYmFjaF9ob3VzaW5nX3RhYmxlKQ0KYGBgDQoNCg0KIyMjIyMgV2UgYXJlIGdvaW5nIHRvIHN0YXJ0IHdpdGggdGhlIG1hcHZpZXcgcGFja2FnZSB0byBnZXQgYSBnbGltcGxzZSBpbnRvIHRoZSANCmBgYHtyfQ0KbWFwdmlldyhiYWNoX2hvdXNpbmcsIHpjb2wgPSAicGVyY2VudF9yZW50ZXJzIiwgemNvbCA9ICJwZXJjZW50X293bmVycyIpDQpgYGA=